This class is meant as an emulation of SCSoundFileView. last mod: 13-jan-08 sciss
Also refer to JSCView for different behaviour affecting all widgets
| no-op / not working | |
| block size | block sizes for decimation are ignored |
| path names | (in the SoundFile object) cannot be relative. use .absolutePath instead. files must be locally accessible |
| lissajou | lissajou style (style == 2) is not supported |
| different behaviour | |
| memory | waveform cache is read / written from harddisk (tmp folder) and not kept completely in RAM |
| resolution | waveform can be displayed at full sample resolution. when zoomed out, peak + RMS are shown |
| extended functionality | |
| action | additional arguments for type of action and parameters |
| cache | management of waveform cache |
| known issues / todo | |
| elasticResizeMode | not yet implemented (this is always _1_ now) |
| read | can only be asynchronous. put in a Routine to wait for completion |
| setData | can only be asynchronous. put in a Routine. |
| data | (getter) not yet implemented |
| performance | graphics update could be more efficiently buffering during scrolling |
| metaAction | does not get evaluated on ctrl+mouseclick/drag |
| background | offscreen image paints transparent pixels black on Linux / Sun Java SE 1.6 (therefore background_ has no effect, and selections are not visible ;-C ) |
Note: please use the abstraction layer GUI.soundFileView if possible! (see GUI)
Note: this implementation is slightly incomplete!
This gadget is a waveform display for sound files. It has facilities for zooming in time and amplitude, handles a timeline cursor and multiple selections.
In the following example, click on the "..." button to select a soundfile from a dialog. use the slider on the right to zoom vertically and the range slider on the bottom to move and zoom horizontally:
(
f = nil;
w = JSCWindow( "Soundfile View", Rect( 300, 300, 770, 270 ));
JSCStaticText( w, Rect( 20, 10, 40, 20 ))
.align_( \right )
.string_( "Path:" );
x = JSCDragSink( w, Rect( 70, 10, 650, 20 ))
.resize_( 2 )
.action_({ arg b;
if( f.notNil, { f.close; f = nil; });
f = SoundFile.new;
f.openRead( b.object );
GUI.useID( \swing, { f.inspect });
a.soundfile = f;
a.gridOn = false;
y.lo = 0;
y.hi = 1;
a.readWithTask( 0, f.numFrames, doneAction: { arg b;
a.gridResolution = (b.soundfile.numFrames / (b.soundfile.sampleRate * 16)).max( 0.1 );
a.gridOn = true;
});
// a.read( 0, f.numFrames ); // warning...
});
JSCButton( w, Rect( 730, 10, 20, 20 ))
.resize_( 3 )
.states_([[ "..." ]])
.action_({ arg b;
SwingDialog.getPaths({ arg paths;
x.object = paths.first;
x.doAction;
}, maxSize: 1 );
});
a = JSCSoundFileView( w, Rect( 20, 40, 700, 180 ))
.resize_( 5 );
a.elasticMode = 1;
a.timeCursorOn = true;
a.timeCursorColor = Color.red;
// a.timeCursorPosition = 2500;
y = JSCRangeSlider( w, Rect( 20, 230, 700, 20 ))
.resize_( 8 )
.action_({ arg b;
a.zoomToFrac( b.range.max( a.bounds.width / a.numFrames.max( 1 )));
if( b.range < 1, { a.scrollTo( b.lo / (1 - b.range) )}); // stupid scrollTo definition
});
JSCSlider( w, Rect( 730, 40, 20, 180 ))
.resize_( 6 )
.value_( 0.5 )
.action_({ arg b;
a.yZoom = b.value.linexp( 0, 1, 0.02, 50.0 );
});
w.front;
)
// x.object = "sounds/a11wlk01.wav".absolutePath; // ! absolutePath // x.doAction; // spills out a warning as SwingOSC necessarily reads asynchronous z = SoundFile.openRead( "sounds/a11wlk01.wav".absolutePath ); a.readFile( z, 0, z.numFrames ); // <soundFile>, <startFrame>, <numFrames>, <block>, <close> z.isOpen; // --> readFile closes the sound file by default // reading again from the previously used sound file a.read( 11025, 22050 ); // <startFrame>, <numFrames>, <block>, <close> // asynchronous read z = SoundFile.openRead( "sounds/SinedPink.aiff".absolutePath ); // <soundFile>, <startFrame>, <numFrames>, <block>, <doneAction> a.readFileWithTask( z, 0, -1, doneAction: { "Yippie!".postln }); // -1 is short for "all frames" // reading again asynchronously from the previously used sound file a.readWithTask( 500, -1, doneAction: { "Once more!".postln }); // <startFrame>, <numFrames>, <block>, <doneAction> // passing in custom data (this doesn't work with SCSoundFileView - why?) a.setData( Signal.chebyFill( 44100, [ 0.3, -0.8, 1.1, -0.95, -0.4 ]));
// waveform style: 0 = normal, 1 = all channels overlayed // (you have to load a stereo or multichannel file to see the difference!) a.style = 1; a.style = 0; a.drawsWaveForm = false; a.drawsWaveForm = true; a.waveColors = [ Color.white ]; // for channel 1 (mono) a.waveColors = [ Color.red, Color.green ]; // for channels 1 and 2 (stereo) etc. a.background = Color.white; a.background = Color.black; // turn on/off time grid and set its resolution a.gridOn = false; a.gridOn = true; a.gridResolution = 0.2; // every 200 milliseconds a.gridColor = Color.green( 0.5 );
// place timeline cursor a.timeCursorPosition = 66666; // in sample frames a.timeCursorOn = false; a.timeCursorOn = true; a.timeCursorColor = Color( 0.5, 0.0, 1.0 );
// make selections a.setSelectionStart( 0, 0 ); a.setSelectionSize( 0, 44100 ); a.setSelectionStart( 1, 88200 ); a.setSelectionSize( 1, 44100 ); a.setSelectionStart( 63, 66150 ); // max. selection index is 64 a.setSelectionSize( 63, 11025 ); a.setSelectionColor( 0, Color.red( alpha: 0.5 )); // selections from the user a.currentSelection = 1; // index of the selection which the user edits with the mouse; index is 0 ... 63 a.setEditableSelectionStart( 1, false ); // now only the selection #1 stop point may be edited a.setEditableSelectionStart( 1, true ); a.setEditableSelectionSize( 1, false ); // now the selection #1 size may not be altered a.setEditableSelectionSize( 1, true );
Note: mouse modifiers for making selections:
Ctrl+Drag : move cursor (but don't touch selection)Shift+Drag : extend selection (but don't touch cursor)Meta+Click : select allCtrl+Shift+Drag : move selection (but don't touch cursor) // XXX these modifiers should be changed?
To track user activity, you can assign a Function to 'action':
// what == \cursor or \selection
// for what == \cursor -> params = [ <newPosition> ]
// for what == \selection -> params = [ <index>, <newStart>, <newSize> ]
a.action = { arg butt, what ... params; ([ what ] ++ params).postln };
(note that the cocoa variant SCSoundFileView does not provide the additional arguments!!)
a.zoomAllOut;
fork { 200.do({ arg i; a.zoomToFrac( ((i+1)/200).pow( 4 )); 0.05.wait; }); };
a.zoom( 0.5 ); // relative (zoom in factor 2)
a.zoom( 2.0 ); // relative (zoom out factor 2)
a.setSelection( 4, [ 20000, 40000 ]); a.zoomSelection( 4 ); // zoom to one selection
fork { 200.do({ arg i; a.yZoom = ((i+1)/100); 0.05.wait; }); }; // y-zoom
In order to speed up waveform calculation, cache management can be globally enabled and configured:
JSCSoundFileView.cacheFolder = "/tmp/swingOSC"; // where waveform cache is to be stored JSCSoundFileView.cacheCapacity = 30; // maximum total size of cache files in megabytes JSCSoundFileView.cacheActive = true; // to activate cache
Note: unless you set your folder to a system temporary folder (like "/tmp"), the cache will not be purged after rebooting the computer. If you wish to erase your cache, set the capacity to 0 after setting the folder.